// fast4j - Fast & Agile Service Tools for Java
// Copyright (C) 2007 Alexandre ROMAN
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

// $Id: ServiceProxyFactoryBean.java 40 2007-03-29 20:57:50Z alexandre.roman $

package fast4j.ejbbridge;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.util.Assert;

import fast4j.ejbbridge.util.MethodUtils;

/**
 * Client-side service proxy. This {@link FactoryBean} implementation enables to
 * connect a client to services defined in a server-side Spring context, exposed
 * by the {@link ServiceBridge} EJB. Just define this bean in a client-side
 * Spring context, and you will have access to theses services.
 * @author alexandre.roman
 */
public class ServiceProxyFactoryBean extends AbstractFactoryBean {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private Class serviceClass;
    private ServiceBridge serviceBridge;
    
    public void setServiceClass(Class serviceClass) {
        this.serviceClass = serviceClass;
    }
    
    public void setServiceBridge(ServiceBridge serviceBridge) {
        this.serviceBridge = serviceBridge;
    }
    
    protected Object createInstance() throws Exception {
        final ClassLoader classLoader = Thread.currentThread()
                .getContextClassLoader();
        final Class[] interfaces = new Class[] { serviceClass };
        final InvocationHandler handler = new ServiceProxyHandler();
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
    
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(serviceClass,
            "The serviceClass property is required and cannot be null");
        Assert.notNull(serviceBridge,
            "The serviceBridge property is required and cannot be null");
        super.afterPropertiesSet();
    }
    
    public Class getObjectType() {
        return serviceClass;
    }
    
    public boolean isSingleton() {
        return false;
    }
    
    /**
     * Internal {@link InvocationHandler} implementation used by service
     * proxies.
     * @author alexandre.roman
     */
    private class ServiceProxyHandler implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            final int hash = MethodUtils.hash(method);
            final ServiceCall call = new ServiceCall(serviceClass.getName(),
                    hash, args);
            
            call.getContext().putAll(ServiceBridgeContextHolder.get());
            log.debug("Service call: {}", call);
            
            return serviceBridge.call(call).getValue();
        }
    }
}
